package com.ycg.biquge.service.impl;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ycg.biquge.common.Result;
import com.ycg.biquge.pojo.Chapter;
import com.ycg.biquge.pojo.Novel;
import com.ycg.biquge.pojo.vo.CategoryVos;
import com.ycg.biquge.pojo.vo.DetailNovelVo;
import com.ycg.biquge.pojo.vo.Recommend;
import com.ycg.biquge.pojo.vo.SimpleNovel;
import com.ycg.biquge.service.ChapterService;
import com.ycg.biquge.service.NovelService;
import com.ycg.biquge.mapper.NovelMapper;
import jakarta.annotation.Resource;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Service;

import java.util.*;

import static com.ycg.biquge.constant.CacheConstant.*;

/**
 * @author lenovo
 * @description 针对表【novel(小说信息表)】的数据库操作Service实现
 * @createDate 2024-04-16 21:28:48
 */
@Service
public class NovelServiceImpl extends ServiceImpl<NovelMapper, Novel>
        implements NovelService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private ChapterService chapterService;

    /**
     * 小说类型
     */
    private final String[] types = {"玄幻奇幻", "武侠仙侠", "都市言情", "历史军事", "网游竞技", "科幻灵异", "女生频道"};


    /**
     * 尝试从缓存中获取热门列表 4 个
     */
    private void getHotsAndTopFromCache(Map<String, List<Novel>> map) {
        getFromCache(map, HOTS_KEY);
        getFromCache(map, TOPS_KEY);
    }

    private void getFromCache(Map<String, List<Novel>> map, String key) {
        String novelsStr = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isBlank(novelsStr)) return;
        List<Novel> novels = JSONUtil.toList(novelsStr, Novel.class);
        map.put(key, novels);
    }

    /**
     * 获取热门小和top推荐小说
     *
     * @return map hots:[]   tops:[]
     */
    @Override
    public Result<Map<String, List<Novel>>> getHotAndTop() {
        // 考虑扩容因子 13 / 3 * 4  初始容量设置为18 可避免自动扩容
        Map<String, List<Novel>> map = new HashMap<>(18);
        // 从redis中获取热门和top小说
        getHotsAndTopFromCache(map);
        // 如果存在则直接返回
        if (map.size() != 0) {
            return Result.ok(map);
        }
        // 不存在, 查询数据库, 算出分值再返回 分值有定时任务, 每晚计算分值并插入数据库
        getHotAndTopFromDB(map);
        // 存入缓存
        hotAndTopCache(map);
        return Result.ok(map);
    }

    private void hotAndTopCache(Map<String, List<Novel>> map) {
        List<Novel> hots = map.get(HOTS_KEY);
        List<Novel> tops = map.get(TOPS_KEY);

        stringRedisTemplate.executePipelined(new SessionCallback<>() {
            @Override
            public <K, V> Object execute(RedisOperations<K, V> operations) throws DataAccessException {
                StringRedisTemplate template = (StringRedisTemplate) operations;
                template.opsForValue().set(HOTS_KEY, JSONUtil.toJsonStr(hots));
                template.opsForValue().set(TOPS_KEY, JSONUtil.toJsonStr(tops));
                return null;
            }
        });
    }

    /**
     * 从数据库中获取hot和top
     */
    private void getHotAndTopFromDB(Map<String, List<Novel>> map) {
        LambdaQueryWrapper<Novel> hotWrapper = new LambdaQueryWrapper<>();
        hotWrapper.select(Novel::getId, Novel::getName, Novel::getAuthor, Novel::getSimple, Novel::getPicture)
                .orderByDesc(Novel::getScore).last("limit 1, 4");
        List<Novel> hots = list(hotWrapper);
        for (Novel hot : hots) {
            String simple = hot.getSimple();
            int randomInt = RandomUtil.randomInt(70, 90);
            hot.setSimple(simple.substring(0, Math.min(randomInt, simple.length())) + "......");
        }
        LambdaQueryWrapper<Novel> topWrapper = new LambdaQueryWrapper<>();
        topWrapper.select(Novel::getId, Novel::getName, Novel::getType)
                .orderByDesc(Novel::getScore).last("limit 4, 9");
        List<Novel> tops = list(topWrapper);
        for (Novel top : tops) {
            top.setType(top.getType().substring(0, 2));
        }
        map.put(HOTS_KEY, hots);
        map.put(TOPS_KEY, tops);

    }

    /**
     * 获取首页 分类列表, 热门
     *
     * @return map: category1:CategoryVos, category2:CategoryVos ...
     */
    @Override
    public Result<Map<String, CategoryVos>> getCategory() {
        Map<String, CategoryVos> map = new HashMap<>(74);
        // 从redis中获取分类热门
        getHomeCategoryFromCache(types, map);
        // 如果存在则直接返回
        if (map.size() > 0) {
            return Result.ok(map);
        }
        // 不存在, 查询数据库, 按照分值降序查询 分值有定时任务, 每晚计算分值并插入数据库
        getHomeCategoryFromDB(types, map);
        // 存入缓存
        categoryCache(map);
        if (map.size() > 0) {
            return Result.ok(map);
        }
        return Result.error("数据查找失败");
    }

    private void categoryCache(Map<String, CategoryVos> map) {
        stringRedisTemplate.opsForValue().set(HOME_CATEGORY_KEY, JSONUtil.toJsonStr(map));
    }

    private void getHomeCategoryFromDB(String[] types, Map<String, CategoryVos> map) {
        for (String type : types) {
            CategoryVos vo = getBaseMapper().getCategoryVo(type);
            if (Objects.nonNull(vo)) {
                String simple = vo.getSimple();
                vo.setSimple(simple.substring(0, Math.min(RandomUtil.randomInt(50, 60), simple.length())) + "......");
                for (Novel novel : vo.getList()) {
                    novel.setType(novel.getType().substring(0, 2));
                }
                map.put(type, vo);
            }
        }
    }

    private void getHomeCategoryFromCache(String[] keys, Map<String, CategoryVos> map) {
        for (String key : keys) {
            String homeCategory = stringRedisTemplate.opsForValue().get(HOME_CATEGORY_KEY + key);
            if (StrUtil.isNotBlank(homeCategory)) {
                CategoryVos vos = JSONUtil.toBean(homeCategory, CategoryVos.class);
                map.put(key, vos);
            }
        }
    }

    /**
     * 获取分类下的小说
     *
     * @param page 当前页, 每页固定15个实例
     * @param type 分类类型
     * @return 小说列表
     */
    @Override
    public Result<List<Novel>> getCategoryList(Integer page, String type) {
        // 从换缓存中查询前15个该分类下的小说 每个分类下缓存3页工45篇novel
        List<Novel> list;
        list = getCategoryListFromCache(page, type);
        // 没有则根据分值分页查询小说
        if (Objects.isNull(list)) {
            list = getCategoryListFromDB(page, type);
        }
        // 存入缓存 只缓存前三页的 定时更新分值
        categoryListCache(page, type, list);
        return Result.ok(list);
    }

    private void categoryListCache(Integer page, String type, List<Novel> list) {
        if (page > 3) return;
        stringRedisTemplate.opsForValue().set(CATEGORY_KEY + type + ":" + page, JSONUtil.toJsonStr(list));
    }

    private List<Novel> getCategoryListFromDB(Integer page, String type) {
        LambdaQueryWrapper<Novel> wrapper = new LambdaQueryWrapper<>();
        wrapper.orderByDesc(Novel::getScore)
                .select(Novel::getPicture, Novel::getName, Novel::getId, Novel::getSimple, Novel::getAuthor)
                .eq(Novel::getType, type);
        return getNovels(page, wrapper);
    }

    private List<Novel> getCategoryListFromCache(Integer page, String type) {
        String categoryStr = stringRedisTemplate.opsForValue().get(CATEGORY_KEY + type + ":" + page);
        if (StrUtil.isBlank(categoryStr)) return null;
        return JSONUtil.toList(categoryStr, Novel.class);
    }

    /**
     * 根据id 获取小说详细信息 包括章节信息
     *
     * @param id 当前小说id
     * @return 小说信息 + 章节列表
     */
    @Override
    public Result<DetailNovelVo> getDetailById(Integer id) {
        DetailNovelVo vo = new DetailNovelVo();
        // 根据id查询小说
        Novel novel = getById(id);
        // 根据id查询章节信息
        LambdaQueryWrapper<Chapter> wrapper = new LambdaQueryWrapper<>();
        wrapper.select(Chapter::getId, Chapter::getTitle)
                .orderByAsc(Chapter::getNum)
                .eq(Chapter::getNovelId, novel.getId());
        List<Chapter> list = chapterService.list(wrapper);
        // 整合返回
        vo.setList(list);
        vo.setNovel(novel);
        return Result.ok(vo);
    }

    /**
     * 根据id 获取小说简要信息
     *
     * @param id 当前小说id
     * @return 简要小说信息
     */
    @Override
    public Result<Novel> getSimpleById(Integer id) {
        LambdaQueryWrapper<Novel> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Novel::getId, id)
                .select(Novel::getId, Novel::getName);
        Novel novel = getOne(wrapper);
        return Result.ok(novel);
    }

    /**
     * 获取首页最近更新和上架的小说列表 每个 30条
     *
     * @return 最近更新小说 和 最新入库小说
     */
    @Override
    public Result<Map<String, List<Novel>>> getNew() {
        Map<String, List<Novel>> map;
        map = getNewFromCache();
        if (map.size() > 0) {
            return Result.ok(map);
        }
        map = getNewFromDB();
        // 保存到缓存
        newCache(map);
        return Result.ok(map);
    }

    /**
     * 获取已经完本的小说
     *
     * @param page 当前页
     * @return 小说列表
     */
    @Override
    public Result<List<Novel>> getFinish(Integer page) {
        // 从换缓存中查询前15个已经完本的小说 每个分类下缓存3页工45篇novel
        List<Novel> list;
        list = getFinishFromCache(page);
        // 没有则根据分值分页查询小说
        if (Objects.isNull(list)) {
            list = getFinishListFromDB(page);
        }
        // 存入缓存 只缓存前三页的 定时更新分值
        finishListCache(page, list);
        return Result.ok(list);
    }

    /**
     * 获取各类型小说排行
     * 每个分类取前50个
     *
     * @return key:小说类型  value:该类型下前50个列表
     */
    @Override
    public Result<Map<String, List<SimpleNovel>>> getRanking() {
        Map<String, List<SimpleNovel>> map = getRankingFromCache();
        if (ObjectUtil.isNotNull(map) && map.size() > 0) {
            return Result.ok(map);
        }
        // 获取常规类型小说
        for (String type : types) {
            List<SimpleNovel> simpleNovels = getRankingByTypes(type);
            map.put(type, simpleNovels);
        }
        // 查询完本小说
        List<SimpleNovel> finishNovels = getRankingByStatus();
        map.put("已经完本", finishNovels);

        // 保存到缓存
        cacheRanking(map);
        return Result.ok(map);
    }

    /**
     * 根据分类查询新书
     *
     * @param type 小说分类
     * @return 小说id+名称
     */
    @Override
    public Result<List<Recommend>> getRecommendByType(String type) {
        List<Recommend> list = getRecommendFromCache(type);
        if (list.size() > 0) return Result.ok(list);

        getRecommendFromDB(type, list);
        if (list.size() == 0) return null;
        cacheRecommend(type, list);
        return Result.ok(list);
    }

    private void cacheRecommend(String type, List<Recommend> list) {
        stringRedisTemplate.opsForValue().set(RECOMMEND_KEY + type, JSONUtil.toJsonStr(list));
    }

    private void getRecommendFromDB(String type, List<Recommend> list) {
        LambdaQueryWrapper<Novel> wrapper = new LambdaQueryWrapper<>();
        wrapper.select(Novel::getId, Novel::getName)
                .eq(Novel::getType, type)
                .orderByDesc(Novel::getCreateTime)
                .last("limit 10");
        List<Novel> novelList = list(wrapper);
        for (Novel novel : novelList) {
            Recommend recommend = new Recommend();
            recommend.setId(novel.getId());
            recommend.setName(novel.getName());
            list.add(recommend);
        }
    }

    private List<Recommend> getRecommendFromCache(String type) {
        List<Recommend> recommends = new ArrayList<>();
        String listStr = stringRedisTemplate.opsForValue().get(RECOMMEND_KEY + type);
        if (StrUtil.isBlank(listStr)) return recommends;
        recommends.addAll(JSONUtil.toList(listStr, Recommend.class));
        return recommends;
    }

    private void cacheRanking(Map<String, List<SimpleNovel>> map) {
        stringRedisTemplate.executePipelined(new SessionCallback<>() {
            @Override
            public <K, V> Object execute(RedisOperations<K, V> operations) throws DataAccessException {
                StringRedisTemplate template = (StringRedisTemplate) operations;
                for (String key : map.keySet()) {
                    template.opsForValue().set(RANKING_KEY + key, JSONUtil.toJsonStr(map.get(key)));
                }
                return null;
            }
        });
    }

    private Map<String, List<SimpleNovel>> getRankingFromCache() {
        // 初始化map
        Map<String, List<SimpleNovel>> map = new HashMap<>(12);
        List<String> keys = stringRedisTemplate.scan(ScanOptions.scanOptions().match(RANKING_KEY + "*").build()).stream().toList();
        if (keys == null || keys.size() == 0) return map;

        List<String> list = stringRedisTemplate.opsForValue().multiGet(keys);

        for (int i = 0; i < keys.size(); i++) {
            assert list != null;
            String key = keys.get(i).substring(15);
            List<SimpleNovel> simpleNovels = JSONUtil.toList(list.get(i), SimpleNovel.class);
            map.put(key, simpleNovels);
        }
        return map;
    }

    private List<SimpleNovel> getRankingByStatus() {
        List<SimpleNovel> finishNovels = new ArrayList<>(50);
        LambdaQueryWrapper<Novel> wrapper = new LambdaQueryWrapper<>();
        // 根据分数查询出前50个
        wrapper.select(Novel::getId, Novel::getName, Novel::getAuthor)
                .eq(Novel::getStatus, "已经完本")
                .orderByDesc(Novel::getScore).last("limit 50");
        List<Novel> list = list(wrapper);
        copy(finishNovels, list);
        return finishNovels;
    }

    private List<SimpleNovel> getRankingByTypes(String type) {
        List<SimpleNovel> simpleNovels = new ArrayList<>(50);
        LambdaQueryWrapper<Novel> wrapper = new LambdaQueryWrapper<>();
        // 根据分数查询出前50个
        wrapper.select(Novel::getId, Novel::getName, Novel::getAuthor)
                .eq(Novel::getType, type)
                .orderByDesc(Novel::getScore).last("limit 50");
        List<Novel> list = list(wrapper);
        copy(simpleNovels, list);
        return simpleNovels;
    }

    private void copy(List<SimpleNovel> simpleNovels, List<Novel> list) {
        for (Novel novel : list) {
            SimpleNovel simpleNovel = new SimpleNovel();
            simpleNovel.setId(novel.getId());
            simpleNovel.setName(novel.getName());
            simpleNovel.setAuthor(novel.getAuthor());
            simpleNovels.add(simpleNovel);
        }
    }

    private void finishListCache(Integer page, List<Novel> list) {
        if (page > 3) return;
        stringRedisTemplate.opsForValue().set(FINISH_KEY + page, JSONUtil.toJsonStr(list));
    }

    private List<Novel> getFinishListFromDB(Integer page) {
        LambdaQueryWrapper<Novel> wrapper = new LambdaQueryWrapper<>();
        wrapper.orderByDesc(Novel::getScore)
                .select(Novel::getPicture, Novel::getName, Novel::getId, Novel::getSimple, Novel::getAuthor)
                .eq(Novel::getStatus, "已经完本");
        return getNovels(page, wrapper);
    }

    private List<Novel> getNovels(Integer page, LambdaQueryWrapper<Novel> wrapper) {
        Page<Novel> novelPage = page(new Page<>(page, 15), wrapper);
        for (Novel novel : novelPage.getRecords()) {
            String simple = novel.getSimple();
            novel.setSimple(simple.substring(0, Math.min(RandomUtil.randomInt(60, 65), simple.length())) + "......");
        }
        return novelPage.getRecords();
    }

    private List<Novel> getFinishFromCache(Integer page) {
        String finish = stringRedisTemplate.opsForValue().get(FINISH_KEY + page);
        if (StrUtil.isBlank(finish)) return null;
        return JSONUtil.toList(finish, Novel.class);
    }

    private void newCache(Map<String, List<Novel>> map) {
        stringRedisTemplate.opsForValue().set(NEW_IN_KEY, JSONUtil.toJsonStr(map.get(NEW_IN_KEY)));
        stringRedisTemplate.opsForValue().set(NEW_UPDATE_KEY, JSONUtil.toJsonStr(map.get(NEW_UPDATE_KEY)));
    }

    private Map<String, List<Novel>> getNewFromDB() {
        Map<String, List<Novel>> map = new HashMap<>();
        LambdaQueryWrapper<Novel> newWrapper = new LambdaQueryWrapper<>();
        newWrapper.orderByDesc(Novel::getCreateTime)
                .select(Novel::getId, Novel::getType, Novel::getName, Novel::getAuthor)
                .last("limit 30");
        putAndFormat(newWrapper, map, NEW_IN_KEY);
        LambdaQueryWrapper<Novel> updateWrapper = new LambdaQueryWrapper<>();
        updateWrapper.orderByDesc(Novel::getUpdateTime)
                .select(Novel::getId, Novel::getType, Novel::getName, Novel::getNewChapter, Novel::getAuthor, Novel::getUpdateTime)
                .last("limit 30");
        putAndFormat(updateWrapper, map, NEW_UPDATE_KEY);
        return map;
    }

    private void putAndFormat(LambdaQueryWrapper<Novel> wrapper, Map<String, List<Novel>> map, String key) {
        List<Novel> list = list(wrapper);
        if (Objects.nonNull(list) && list.size() > 0) {
            for (Novel novel : list) {
                if (StrUtil.isBlank(novel.getType())) break;
                novel.setType(novel.getType().substring(0, 2));
            }
            map.put(key, list);
        }
    }

    private Map<String, List<Novel>> getNewFromCache() {
        Map<String, List<Novel>> map = new HashMap<>();
        String newIn = stringRedisTemplate.opsForValue().get(NEW_IN_KEY);
        if (StrUtil.isNotBlank(newIn)) {
            List<Novel> newInList = JSONUtil.toList(newIn, Novel.class);
            map.put(NEW_IN_KEY, newInList);
        }

        String newUpdate = stringRedisTemplate.opsForValue().get(NEW_UPDATE_KEY);
        if (StrUtil.isNotBlank(newUpdate)) {
            List<Novel> newUpdateList = JSONUtil.toList(newUpdate, Novel.class);
            map.put(NEW_UPDATE_KEY, newUpdateList);
        }
        return map;

    }
}




